/*
 * Copyright 2009-2010 the original author or authors.
 *
 * Licensed under the Apache license, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.internna.iwebjtracker.model.dao;

import java.util.Map;
import java.util.List;
import java.util.HashMap;
import org.internna.iwebjtracker.model.Activity;
import org.internna.iwebjtracker.model.TrackerUser;
import org.internna.iwebjtracker.model.DefaultIssue;
import org.internna.iwebjtracker.model.DefaultModule;
import org.internna.iwebjtracker.model.DefaultProject;
import org.internna.iwebjtracker.model.DefaultVersion;
import org.internna.iwebjtracker.activity.ActivityHolder;
import org.internna.iwebjtracker.activity.model.RecordedActivity;
import org.internna.iwebjtracker.activity.model.RecordedActivity.ActivityType;
import org.internna.iwebmvc.dao.DAO;
import org.internna.iwebmvc.model.User;
import org.internna.iwebmvc.model.DomainEntity;
import org.internna.iwebmvc.security.UserManager;
import org.internna.iwebmvc.dao.DelegateProxyDAO;
import org.internna.iwebmvc.utils.CollectionUtils;
import org.internna.iwebmvc.model.security.UserImpl;
import org.springframework.transaction.annotation.Transactional;

import static org.internna.iwebjtracker.activity.model.RecordedActivity.ActivityType.*;

/**
 * Proxies a DAO to persist activities based on the kind of operations intercepted.
 *
 * @author Jose Noheda
 * @since 1.0
 */
public class ActivityDao extends DelegateProxyDAO {

    private UserManager userManager;

    /**
     * Spring constructor injection point.
     *
     * @param dao any not null DAO instance
     */
    public ActivityDao(DAO dao) {
        super(dao);
    }

    /**
     * Spring setter injection point.
     *
     * @param userManager any
     */
    public final void setUserManager(UserManager userManager) {
        this.userManager = userManager;
    }

    /**
     * Intercepts updates to domain entities checking for stored activities.
     *
     * @param entity any
     * @return
     */
    @Override public DomainEntity update(DomainEntity entity) {
        DomainEntity updated = super.update(entity);
        if (updated instanceof ActivityHolder) {
            ActivityHolder<?, ?> holder = (ActivityHolder<?, ?>) updated;
            for (RecordedActivity activity : holder.getPerformedActivities()) {
                create(activity, false);
            }
            holder.clear();
            flush();
        }
        return updated;
    }

    /**
     * Intercepts create operation checking for suitable entities (project, version, module)
     * and persisting a matching activity.
     *
     * @param entity any domain entity
     * @param flush true if the database should be synchronized after this operation
     */
    @Transactional
    @Override public final void create(DomainEntity entity, boolean flush) {
        super.create(entity, false);
        Activity activity = generateActivity(entity);
        if (activity != null) {
            setOriginator(activity);
            setActivitySecurity(activity);
            updateReputation(activity);
            create(activity, flush);
        } else if (flush) {
            flush();
        }
    }

    /**
     * Establish the user that is performing the action
     *
     * @param activity any
     */
    protected final void setOriginator(Activity activity) {
        if (activity != null) {
            User user = getCurrentUser();
            if (user != null) {
                Map<String, Object> params = new HashMap<String, Object>(1);
                params.put("username", user.getUsername());
                List<TrackerUser> users = findByNamedQuery(TrackerUser.FIND_BY_USERNAME, 0, 1, params);
                if (CollectionUtils.isNotEmpty(users)) {
                    activity.setOriginator(users.get(0));
                }
            }
        }
    }

    /**
     * Generates a new activity for a suitable entity.
     *
     * @param entity any
     */
    protected Activity generateActivity(DomainEntity entity) {
        Activity activity = null;
        if (entity instanceof DefaultProject) {
            activity = generateProjectCreationActivity((DefaultProject) entity);
        } else if (entity instanceof DefaultModule) {
            activity = generateModuleCreationActivity((DefaultModule) entity);
        } else if (entity instanceof DefaultVersion) {
            activity = generateVersionCreationActivity((DefaultVersion) entity);
        } else if (entity instanceof DefaultIssue) {
            activity = generateIssueCreationActivity((DefaultIssue) entity);
        }
        return activity;
    }

    /**
     * Creates an activity that matches a project creation.
     *
     * @param project a non null project
     * @return a non null activity
     */
    protected final Activity generateProjectCreationActivity(DefaultProject project) {
        return createActivity(project, PROJECT_CREATION);
    }

    /**
     * Creates an activity that matches a module or component entity.
     *
     * @param module a non null module
     * @return a non null activity
     */
    protected final Activity generateModuleCreationActivity(DefaultModule module) {
        ActivityType type = module.getParentModule() == null ? MODULE_CREATION : COMPONENT_CREATION;
        return createActivity(module.getParentProject(), type);
    }

    /**
     * Creates an activity that matches a version or milestone entity.
     *
     * @param version a non null version
     * @return a non null activity
     */
    protected final Activity generateVersionCreationActivity(DefaultVersion version) {
        ActivityType type = version.getParentVersion() == null ? VERSION_CREATION : MILESTONE_CREATION;
        return createActivity(version.getParentProject(), type);
    }

    /**
     * Creates an activity that matches an issue entity.
     *
     * @param issue a non null issue instance
     * @return a non null activity
     */
    protected final Activity generateIssueCreationActivity(DefaultIssue issue) {
        return createIssueActivity(issue, ISSUE_CREATION);
    }

    /**
     * Creates an activity that holds both project and issue parents.
     *
     * @param issue a non null issue instance with a parent project
     * @param type any
     * @return a non null activity
     */
    protected final Activity createIssueActivity(DefaultIssue issue, ActivityType type) {
        Activity activity = createActivity(issue.getParentProject(), type);
        activity.setIssue(issue);
        return activity;
    }

    /**
     * Creates a fully initialized activity object for this type.
     *
     * @param project a not null entity
     * @param type a not null type
     * @return a non null activity
     */
    protected final Activity createActivity(DefaultProject project, ActivityType type) {
        Activity activity = createActivity(project);
        activity.setType(type);
        return activity;
    }

    /**
     * Creates an activity and sets all default values.
     *
     * @param project any not null
     * @return a not null activity
     */
    protected final Activity createActivity(DefaultProject project) {
        Activity activity = new Activity();
        activity.setProject(project);
        return activity;
    }

    private void setActivitySecurity(Activity activity) {
        DefaultProject project = activity.getProject();
        activity.setPublicView(project.isPublicView());
        UserImpl user = activity.getOriginator() == null ? null : activity.getOriginator().getSecurityUser();
        if ((user != null) && (!user.isAnonymous())) {
            activity.addOwner(user);
        } else {
            for (UserImpl owner : project.getOwners()) {
                activity.addOwner(owner);
            }
        }
        if (!activity.isPublicView()) {
            for (UserImpl viewer : project.getViewers()) {
                activity.addViewer(viewer);
            }
        }
    }

    /**
     * Increments (or not) the user reputation based on his activity.
     *
     * @param activity any
     */
    protected final void updateReputation(Activity activity) {
        if ((activity != null) && (activity.getOriginator() != null)) {
            int increment = (int) (5 / activity.getActivityPriority().getRating());
            activity.getOriginator().increaseReputation(increment);
        }
    }

    private User getCurrentUser() {
        try {
            return userManager.getActiveUser();
        } catch (Exception ex) {
            return null;
        }
    }

}
